#pragma once

#include <Wire.h>
#include "limits.h"
#include "UltrasonicReceiver.h"
#include "Common.h"

#define IO_ADDR_BUTTONS_SNAIL_SHELL IO_ADDR1

#define BUTTON_ORANGE_POS 0x1
#define BUTTON_RED_POS 0x2
#define BUTTON_VIOLETT_POS 0x4
#define BUTTON_YELLOW_POS 0x8
#define BUTTON_BLUE_POS 0x10

#define BUTTON_VIOLETT_IDX 0
#define BUTTON_YELLOW_IDX 1
#define BUTTON_BLUE_IDX 2
#define BUTTON_ORANGE_IDX 3
#define BUTTON_RED_IDX 4

extern void InterruptHandler1();

class ShellButtons
{

public:

  inline static void Init() 
  {
    Wire.beginTransmission(IO_ADDR_BUTTONS_SNAIL_SHELL);
    Wire.write(0xFF);    // Alle Ports als Eingang (HIGH)
    Wire.endTransmission(true);

    FillButtonMappingArray();

    attachInterrupt(INTERRUP_NR, InterruptHandler1, INTERRUP_NR_TYPE );  
    
    InitMidi();
  }

  static void InitMidi();
  static void SendNoteOn( byte pitch,byte volume,byte channel );
  static void SendNoteOff( byte pitch,byte volume,byte channel );

  inline static void FillButtonMappingArray() 
  {
    ButtonMidiNoteMapping[BUTTON_YELLOW_IDX]  = 60;
    ButtonMidiNoteMapping[BUTTON_ORANGE_IDX]  = 62;
    ButtonMidiNoteMapping[BUTTON_RED_IDX]     = 64;
    ButtonMidiNoteMapping[BUTTON_VIOLETT_IDX] = 65;
    ButtonMidiNoteMapping[BUTTON_BLUE_IDX]    = 67;
  }

  inline static void DoUpdateStateButton(byte BitMapShellButtons, byte index, byte pos) 
  {
    bool state = ButtonStatesPreset[index] = BitMapShellButtons & pos;

    if ( !ButtonStates[index] && state ) {
      ButtonStateLastEventTime[index] = globals::CurrentLoopTimeInMs;
/*      
      Serial.print( index );
      Serial.print( " <- PRESSED " );
      Serial.println( ButtonStateLastEventTime[index] );
*/      
    }
    if (ButtonStates[index] && !state) {
      ButtonStateLastEventTime[index] = globals::CurrentLoopTimeInMs;
/*      
      Serial.print( index );
      Serial.print( " <- RELEASED " );
      Serial.println( ButtonStateLastEventTime[index] );
*/
    }
  }

  inline static void DoUpdateState() 
  {

    Wire.beginTransmission(IO_ADDR_BUTTONS_SNAIL_SHELL);
    Wire.write(0xFF);    // Alle Ports als Eingang (HIGH)
    Wire.requestFrom(IO_ADDR_BUTTONS_SNAIL_SHELL, 1);  // Ein Byte (= 8 Bits) anfordern
    while(Wire.available() == 0);    // Warten, bis Daten verfügbar 
    // Serial.print("Eingelesen: ");
    byte BitMapShellButtons = ~(Wire.read() >> 3); // die ersten 3 interessieren uns nicht für die buttons
    Wire.endTransmission(true);

    Wire.beginTransmission(IO_ADDR_BUTTONS_SNAIL_SHELL);
    Wire.write(0xFF);    // Alle Ports als Eingang (HIGH)
    Wire.endTransmission(true);

    DoUpdateStateButton(BitMapShellButtons, BUTTON_ORANGE_IDX, BUTTON_ORANGE_POS);
    DoUpdateStateButton(BitMapShellButtons, BUTTON_RED_IDX,    BUTTON_RED_POS);
    DoUpdateStateButton(BitMapShellButtons, BUTTON_VIOLETT_IDX,BUTTON_VIOLETT_POS);
    DoUpdateStateButton(BitMapShellButtons, BUTTON_YELLOW_IDX, BUTTON_YELLOW_POS);
    DoUpdateStateButton(BitMapShellButtons, BUTTON_BLUE_IDX,   BUTTON_BLUE_POS);

    attachInterrupt(INTERRUP_NR, InterruptHandler1, INTERRUP_NR_TYPE );    
  }

  inline static void CheckSinglePreset(byte index) 
  {
    if ( globals::CurrentLoopTimeInMs - 5 > ButtonStateLastEventTime[index] ) // bouncetime 5 msec
    {

      ButtonStateLastEventTime[index] = ULONG_MAX;

      if ( ButtonStates[index] !=  ButtonStatesPreset[index] ) 
      {
        // now it's clear, the state is changed
        ButtonStates[index] =  ButtonStatesPreset[index];

        IsStateChanged = true;
        /*
        DEBUG("IsStateChanged: "); 
        DEBUG( IsStateChanged );        
        DEBUG( " index:" );
        DEBUG( index );
        DEBUG( " value:" );
        DEBUG( ButtonStates[index] );
        DEBUGLN();
        */
        DoMidi( index, ButtonStates[index] );

      }
    }  
  }

  inline static void CheckPresets() {
    CheckSinglePreset(0);
    CheckSinglePreset(1);
    CheckSinglePreset(2);
    CheckSinglePreset(3);
    CheckSinglePreset(4);
  }

  inline static void UpdateState() 
  {
    CheckPresets();

    if (LastKnownInterruptCounter == InterruptCounter) {
      return;
    }

    LastKnownInterruptCounter = InterruptCounter;
    DoUpdateState();

    return;
  }

  ///
  /// See what's changed and send Midi Data
  /// if something has changed
  ///
  inline static void DoMidi(byte index, bool button_state ) 
  {
    byte pitch = ButtonMidiNoteMapping[index] + globals::TransposeOffset;
    byte volume = (byte)map(
      UltrasonicReceiver::Distance,
      UltrasonicReceiver::MinimumRange,
      UltrasonicReceiver::MaximumRange,
      127,
      0);

    if (button_state) {
       SendNoteOn(pitch,volume,1); // Send a Note (pitch 42, velo 127 on channel 1)
    } else {
       SendNoteOff(pitch,0,1);
    }

//    DEBUG(button_state ? "1," : "0," ); DEBUG(pitch); DEBUG(","); DEBUG(volume); DEBUGLN();
  }

  inline static void SendAllNotesOff() 
  {
    
    for( byte pitch=0; pitch<128; pitch++) 
    {
      SendNoteOn(pitch,0,1);
      SendNoteOff(pitch,0,1);
    }
    
  }


  static volatile byte InterruptCounter;
  static volatile byte LastKnownInterruptCounter;
  static volatile bool IsStateChanged;

  static volatile bool ButtonStates[5];
  static volatile bool ButtonStatesPreset[5];
  static volatile unsigned long ButtonStateLastEventTime[5];

  ///
  /// Which Midi Note should be played if button is pressed?
  /// 
  static volatile byte ButtonMidiNoteMapping[5];
};















